/** This class makes all xml-parsing jobs 
 *  @author ChrM */

package info.chrm.jtalkerserver;

import java.awt.*;
import java.io.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.parsers.*;
import java.util.*;

public class JTalkerParser extends DefaultHandler {

   /** MEMBER tag includes some members informations.
       This tag provides follows properties: NAME [, STATUS, PASSWD] */
   public static final String MEMBER = "MEMBER";
   public static final String MEMBER_NAME = "name";
   public static final String MEMBER_STATUS = "status";
   public static final String MEMBER_PASSWD = "passwd";
   public static final String MEMBER_IP = "ip";
   public static final String MEMBER_TYPE = "type";
   public static final String MEMBER_SID = "sid";
   
  
   /** NEW_MEMBER tag for registered users */
   public static final String NEW_MEMBER = "NEW_MEMBER";
   
   /** MESSAGE tag includes some messages.
       This tag provides follows properties: NAME, CONTENT, SENDER, STATUS, TO, TYPE */
   public static final String MESSAGE = "MESSAGE";
   public static final String MESSAGE_CONTENT = "content";
   public static final String MESSAGE_SENDER = "sender";
   public static final String MESSAGE_STATUS = "status";
   public static final String MESSAGE_TO = "to";
   public static final String MESSAGE_TYPE = "type";
   public static final String MESSAGE_SUBJECT = "subject";
   public static final String MESSAGE_ID = "id";
   
   /** Old style server messages: Error or Info */   
   public static final String ERROR = "ERROR";
   
   /** GLOBAL_USER tag for registered users */
   public static final String GLOBAL_USER = "GLOBAL_USER";
   
   /** Constructor */
   public JTalkerParser() {
   }

   /** Starts parsing */
   public boolean parsing(String _xmlStream) {
      // reset
      this.listMembers.clear();
      this.listMessages.clear();
      this.lastError = null;
      this.isNewMember = false;
      this.lastCommand = null;
      this.lastCommandID = null;
      
      try { 
	 SAXParserFactory fct = SAXParserFactory.newInstance();
	 fct.setValidating(false);
	 SAXParser saxParser = fct.newSAXParser();
	 InputStream is = new ByteArrayInputStream(_xmlStream.getBytes("UTF-8"));
	 saxParser.parse(is, this);
      } catch (Exception _e) { 
	 System.out.println("***: " + _e.toString());
	 return false;
      } 

      return true;
   }

   /** Starts parsing of file */
   public boolean parsing(File _globalUsers) {
      // reset
      this.lastError = null;
      this.isNewMember = false;

      if(!_globalUsers.canRead()) {
	 this.lastError = "Can't load file";
	 return false;
      }
      
      try { 
	 SAXParserFactory fct = SAXParserFactory.newInstance();
	 fct.setValidating(false);
	 SAXParser saxParser = fct.newSAXParser();
	 saxParser.parse(_globalUsers, this);
      } catch (Exception e) { 
	 //e.printStackTrace();
	 return false;
      } 

      return true;
   }

   //---------------------------------------------------------------
   /** Gets the content of the message message */
   public String getMessage() {
      //return this.messageContent;
      if(this.getAMessage(0) != null)
	 return this.getAMessage(0).content;
      else return null;
   }

   /** Gets the type of the Message */
   public String getMessageType() {
      if(this.getAMessage(0) != null)
	 return this.getAMessage(0).messageType;
      else return null;
   }
   
   /** Gets the ID of the Message */
   public String getMessageID() {
      if(this.getAMessage(0) != null)
	 return this.getAMessage(0).messageID;
      else return null;
   }
   
   /** Gets the subject of the Message */
   public String getMessageSubject() {
      if(this.getAMessage(0) != null)
	 return this.getAMessage(0).messageSubject;
      else return null;
   }

   /** Gets the first Sender (on server site) */
   public String getSender() {
      //return this.messageSender;
      if(this.getAMessage(0) != null)
	 return this.getAMessage(0).sender;
      else return null;
   }

   /** Gets the first sender status (on server site; in the message Tag)*/
   public String getStatus() {
      //return this.messageSenderStatus;
      if(this.getAMessage(0) != null)
	 return this.getAMessage(0).senderStatus;
      else return null;
   }

   /** Gets the first sender status (in the member Tag).
    *  Should be used only for update the own status! */
   public String getMemberStatus() {
      if(this.listMembers.size() == 1)
	 return ((OneMember)this.listMembers.get(0)).status;
      else 
	 return null;
   }

   /** Gets a list of Receivers for the first message (separated by double semikolon: ';;')*/
   public String getRecs() {
      //return this.messageReceivers;
      if(this.getAMessage(0) != null)
	 return this.getAMessage(0).messageReceivers;
      else return null;
   }

   /** Gets the recieved command */
   public String getCommand() {
      return this.lastCommand;
   }
   
   /** Gets recieved commands ID */
   public String getCommandID() {
      return this.lastCommandID;
   }

   /** Gets a member by number */
   public OneMember getAMember(int _index) {
      try { 
	 return (OneMember)this.listMembers.get(_index);
      } catch (Exception e) { 
	 return null;
      } 
   }

   /** Gets a message by number */
   public OneMessage getAMessage(int _index) {
      try { 
	 return (OneMessage)this.listMessages.get(_index);
      } catch (Exception e) { 
	 return null;
      } 
   }

   /** Gets a global user by number */
   public GlobalUser getAGlobalUser(int _index) {
      try { 
	 return (GlobalUser)this.listGlobalUsers.get(_index);
      } catch (Exception e) { 
	 return null;
      } 
   }
   
   /** Gets number of members */
   public int getMemberNum() {
      return this.listMembers.size();
   } 

   /** Gets number of messages */
   public int getMessagesNum() {
      return this.listMessages.size();
   } 

   /** Gets number of global users */
   public int getGlobalUserNum() {
      return this.listGlobalUsers.size();
   }

   /** Gets the last Error */
   public String getError() {
      return this.lastError;
   }

   /** Is this a accout-creation? */
   public boolean isNewMember() {
      return this.isNewMember;
   }

   /** Splits Recs to an ArrayList and get it */
   public String [] getRecsAsArray(int _index) {
      return this.getAMessage(_index).messageReceivers.split(";;");      
   }

   /** Splits Recs to an ArrayList and get it (for the first message) */
   public String [] getRecsAsArray() {
      return this.getAMessage(0).messageReceivers.split(";;");      
   }
   //---------------------------------------------------------------
 
   /** SAX-methods */
   public void startElement(String _nsURI, String _sName, String _qName, Attributes _attrs) {
      this.currentTag = _qName;
      
      if(this.currentTag.equalsIgnoreCase(MESSAGE)) {
	 // check message type
	 String tmpType = _attrs.getValue(MESSAGE_TYPE);
	 if(tmpType != null && tmpType.equalsIgnoreCase("error")) {
	    this.lastError = _attrs.getValue(MESSAGE_CONTENT);
	 } else if(tmpType != null && 
		   tmpType.equalsIgnoreCase("command") &&
		   _attrs.getValue(MESSAGE_TO) == null) {
	    this.lastCommand = _attrs.getValue(MESSAGE_CONTENT);
            this.lastCommandID = _attrs.getValue(MESSAGE_ID);
	 } else	{
	    this.listMessages.add( new OneMessage(
			        _attrs.getValue(MESSAGE_CONTENT),
				_attrs.getValue(MESSAGE_SENDER),
				_attrs.getValue(MESSAGE_STATUS),
				_attrs.getValue(MESSAGE_TO),
				tmpType,
				_attrs.getValue(MESSAGE_SUBJECT),
				_attrs.getValue(MESSAGE_ID)) );
	 }
	 
      } else if(this.currentTag.equalsIgnoreCase(this.MEMBER)) {
	 // A Member list is received
	 this.listMembers.add( new OneMember(
			      _attrs.getValue(MEMBER_NAME),
			      _attrs.getValue(MEMBER_STATUS),
			      _attrs.getValue(MEMBER_PASSWD),
			      _attrs.getValue(MEMBER_IP),
			      _attrs.getValue(MEMBER_TYPE),
			      _attrs.getValue(MEMBER_SID)) );
      } else if(this.currentTag.equalsIgnoreCase(ERROR)) {
	 // OBSOLETE: An Error occured (from Client)
	 this.lastError = _attrs.getValue("message");
      //} else if(this.currentTag.equalsIgnoreCase(this.INFO)) {
	 // OBSOLETE: Server sends an info message
      } else if(this.currentTag.equalsIgnoreCase(GLOBAL_USER)) {
	 // A Global User list is parsed from a File
	 this.listGlobalUsers.add( new GlobalUser(
			           _attrs.getValue("name"),
			           _attrs.getValue("passwd"),
				   _attrs.getValue("role"),
				   _attrs.getValue("contacts"),
				   false) );
      } else if(this.currentTag.equalsIgnoreCase(NEW_MEMBER)) {
	 // New Member try to register..
	 this.isNewMember = true;
	 this.listMembers.add( new OneMember(
			      _attrs.getValue(MEMBER_NAME),
			      null,
			      _attrs.getValue(MEMBER_PASSWD),
			      null,
			      null,
			      null) );
      }
   }

   /** SAX-methods */
   public void characters(char[] ch, int start, int length) {
   }

   /** SAX-methods */
   public void endElement(String _uri, String _localName, String _qName) {
   }

   /** SAX-error-methods */
   public void error(SAXParseException _e) {
      System.out.println("*** SAX-Parser Error: " + _e.toString());
   }

   /** SAX-error-methods */
   public void fatalError(SAXParseException _e) {
      System.out.println("*** SAX-Parser Error: " + _e.toString());
   }

   // private members
   private String currentTag = null;
   
   private String lastError = null;
   private String lastCommand = null;
   private String lastCommandID = null;
   private boolean isNewMember = false;
   //private String messageContent = null;
   //private String messageSender = null;
   //private String messageSenderStatus = null;
   //private String messageReceivers = null;
   private ArrayList listMembers = new ArrayList();
   private ArrayList listMessages = new ArrayList();
   private ArrayList listGlobalUsers = new ArrayList();

   /** This inner class represents a member */
   public class OneMember {
      public OneMember(String _name, String _status, String _passwd, String _ip, String _type, String _sid) {
	 this.name = _name;
         
         if(_status != null)
	    this.status = _status;
         else
            this.status = "normal";

	 this.passwd = _passwd;
	 this.ip = _ip;
	 
         if(_type != null)
	    this.type = _type;
	 else 
	    this.type = JTalkerMember.USER_TYPE_PERSISTENT;
	 
         this.sid = _sid;
      }
      
      public String name = null;    // like "UnserA"
      public String status = null;  // "normal", "busy", "robot"
      public String passwd = null;  // "xxx"
      public String ip = null;	    // "127.0.0.1"
      public String type = JTalkerMember.USER_TYPE_PERSISTENT;
      public String sid = null;
   }

   /** This inner class represents a message */
   public class OneMessage {
      public OneMessage(String _content,
			String _sender,
			String _senderStatus,
			String _messageReceivers,
			String _messageType,
			String _messageSubject,
                        String _messageID) {

	 this.content = _content;
	 this.sender = _sender;
	 this.senderStatus = _senderStatus;
	 this.messageReceivers = _messageReceivers;
	 this.messageType = _messageType;
	 this.messageSubject = _messageSubject;
         this.messageID = _messageID;
      }

      public String content = null;	     // Text
      public String sender = null;	     // Member name
      public String senderStatus = null;     // "normal", "busy", "robot"
      public String messageReceivers = null; // "UserA;;UserB;;UserC"
      public String messageType = null;	     // "normal", "error", "info", "command"
      public String messageSubject = null;   // "picture.jpg"
      public String messageID = null;   // "xyz123"
   }

   /** Method for simple using the internal class objects */
   public GlobalUser createGlobalUserObject(String _name, String _passwd, String _role, String [] _contacts) {
      return new GlobalUser(_name, _passwd, _role, _contacts, true);
   }

   /** This inner class represents a global user on the server */
   public class GlobalUser {
      public static final String USER = "user";
      public static final String ADMIN = "admin";
      public static final String ROOT = "root";
      public static final String CONTACTS_ALL = "*";
      

      /** Standard C'tor */
      public GlobalUser(String _name, String _passwd, String _role, boolean _makeHash) {
	 this.name = _name;
	 
	 // Create hash only, if no reading from user file!
	 if(!_makeHash)
	 	this.passwd = _passwd;
	 else
		this.passwd = JTalkerServer.makeHash(_passwd);
	 
	 this.setRole(_role);
      }
      
      /** C'tor. Contacts as array */
      public GlobalUser(String _name, String _passwd, String _role, String [] _contacts, boolean _makeHash) {
	 this(_name, _passwd, _role, _makeHash);
	 this.setContacts(_contacts);
      }

      /** C'tor. Contacts as ;; List */
      public GlobalUser(String _name, String _passwd, String _role, String _contacts, boolean _makeHash) {
	 this(_name, _passwd, _role, _makeHash);
	 this.contactList = _contacts;
      }

      /** Sets new Role */
      public boolean setRole(String _newRole) {
	 if(_newRole == null)
	    return false;
	 
	 if(_newRole.equals(USER) || _newRole.equals(ADMIN) || _newRole.equals(ROOT)) {
	    this.role = _newRole;
	    return true;
	 } else
	    return false;
      }

      /** Gets users role */
      public String getRole() {
	 return this.role;
      }

      /** Sets contacts */
      public boolean setContacts(String [] _contacts) {
	 if(_contacts == null || _contacts.length == 0)
	    return false;

	 this.contactList = "";
	 for( int i = 0; i < _contacts.length; i++ ) {
	    this.contactList += (_contacts[i] + ";;");
	 }

	 return true;
      }

      /** Adds a new contact */
      public boolean addContact(String _contact) {
	 if(_contact == null)
	    return false;
	 	    
	 this.contactList += (_contact + ";;");
	    return true;
      }

      /** Gets users contacts as ";;" separated string */
      public String getContacts() {
	 return this.contactList;	  
      }

      /** Gets the name */
      public String getName() {
	 return name;
      }

      /** Gets passwd */
      public String getPasswd() {
	 return passwd;
      }
      
      private String name = null;
      private String passwd = null;
      private String role = GlobalUser.USER; // Users Role like "ROOT" "ADMIN" "USER"
      private String contactList = GlobalUser.CONTACTS_ALL; // ";;"-separated list of contacts like "hansi;;joe;;tux" or "*" for all
   }
}
